package com.code.learn.Ipersistance.sqlSession;

import com.code.learn.Ipersistance.config.Configuration;
import com.code.learn.Ipersistance.config.MappedStatement;
import com.code.learn.Ipersistance.util.GenericTokenParser;
import com.code.learn.Ipersistance.util.ParameterMapping;
import com.code.learn.Ipersistance.util.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Nina
 * @create 2020/12/8 下午3:09
 * 默认的Executor的实现类
 * 这里是对数据库进行底层的CURD操作
 */
public class SimpleExecutor implements Executor{
    private Configuration configuration;

    public SimpleExecutor(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 注意：这个方法中的反射需要重点看
     * jdbc的操作：连接数据库->sql预处理->执行操作数据库->处理返回结果集
     * @param statementId  namespace.id
     * @param params 不定数量的参数
     * @param <E>
     * @return
     * @throws Exception
     */
    @Override
    public <E> List<E> query(String statementId, Object... params) throws Exception {

        // 1. 创建数据库Connection
        Connection connection = configuration.getDataSource().getConnection();

        // 2. sql预处理
        // 2.1 根据statementId获取MappedStatement，用于对方法各个参数的整合
        MappedStatement mappedStatement = configuration.getMappedStateMentMap().get(statementId);
        // 2.2 基于三个util类操作   将占位符以？形式还原sql -> 给各个参数赋值 -> 封装预处理对象
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}", parameterMappingTokenHandler);
        // 这里parse最终是将sql整合成select * from user where id=?
        String parse = genericTokenParser.parse(mappedStatement.getSql());

        // 2.3 反射处理sql对应参数的传参
        // 获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(parse);
        // 解析sql中需要传入的参数值：parameterMappings content是id，name等字段名，即#{id}中的字段名
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        // 根据参数类型反射获取class
        Class<?> parameterTypeClass = getClassType(mappedStatement.getParameterType());
        // 预处理拼接sql参数
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String name = parameterMapping.getContent();

            // 注意：反射重点看
            Field declaredField = parameterTypeClass.getDeclaredField(name);
            // 暴力访问，即使是私有属性，依然需要访问
            declaredField.setAccessible(true);
            // 获取到传参类型中对应字段的值
            Object value = declaredField.get(params[0]); //获取到值

            preparedStatement.setObject(i+1, value);
        }

        // 3. 执行数据库操作
        ResultSet resultSet = preparedStatement.executeQuery();
        // 4. 处理结果集
        // 4.1 获取到返回类型，作为映射返回使用
        Class<?> resultTypeClass = getClassType(mappedStatement.getResultType());

        ArrayList<Object> objects = new ArrayList<>();
        // 4.2 封装返回集合
        while (resultSet.next()){
            Object o = resultTypeClass.newInstance();
            // 获取返回结果集的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                // 字段名 id
                String columnName = metaData.getColumnName(i);

                // 字段的值 1
                Object value = resultSet.getObject(columnName);

                // 注意：使用PropertyDescriptor类反射或内省，根据数据库表和实体的对应关系，完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                // 获取到setter方法，如setId()
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 代理反射将该类的setter方法读取进value的值
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }

    @Override
    public Object queryOne(String statementId, Object... params) throws Exception {
        // 创建数据库Connection
        Connection connection = configuration.getDataSource().getConnection();

        // 根据statementId获取MappedStatement
        MappedStatement mappedStatement = configuration.getMappedStateMentMap().get(statementId);
        // 整合sql语句
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}", parameterMappingTokenHandler);
        // 这里parse最终是将sql整合成select * from user where id=?
        String parse = genericTokenParser.parse(mappedStatement.getSql());
        // 解析sql中需要传入的参数值：parameterMappings content是id，name等字段名，即#{id}中的字段名
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        // 获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(parse);

        // 根据参数类型反射获取class
        Class<?> parameterTypeClass = getClassType(mappedStatement.getParameterType());
        // 预处理拼接sql参数
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String name = parameterMapping.getContent();

            // 反射，重要
            Field declaredField = parameterTypeClass.getDeclaredField(name);
            declaredField.setAccessible(true);
            Object value = declaredField.get(params[0]); //获取到值

            preparedStatement.setObject(i+1, value);
        }

        // 执行
        ResultSet resultSet = preparedStatement.executeQuery();
        Class<?> resultTypeClass = getClassType(mappedStatement.getResultType());

        Object object = null;
        // 返回结果集
        while (resultSet.next()){
            Object o = resultTypeClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                // 字段名
                String columnName = metaData.getColumnName(i);

                // 字段的值
                Object value = resultSet.getObject(columnName);

                // 使用反射或内省，根据数据库表和实体的对应关系，完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            object = o;
        }

        return object;
    }
    @Override
    public void update(String statementId, Object... params) throws Exception {
        Connection connection = configuration.getDataSource().getConnection();
        MappedStatement mappedStatement = configuration.getMappedStateMentMap().get(statementId);


        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}", parameterMappingTokenHandler);
        // 这里parse最终是将sql整合成select * from user where id=?
        String parse = genericTokenParser.parse(mappedStatement.getSql());
        PreparedStatement preparedStatement = connection.prepareStatement(parse);

        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        // 根据参数类型反射获取class
        Class<?> parameterTypeClass = getClassType(mappedStatement.getParameterType());
        // 预处理拼接sql参数
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String name = parameterMapping.getContent();

            // 反射，重要
            Field declaredField = parameterTypeClass.getDeclaredField(name);
            declaredField.setAccessible(true);
            Object value = declaredField.get(params[0]); //获取到值

            preparedStatement.setObject(i+1, value);
        }

        preparedStatement.executeUpdate();
    }

    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if(parameterType != null){
            return Class.forName(parameterType);
        }
        return null;
    }

}
